/*
 *  Copyright © 2014 CIB Co.Ltd
 *  * All right reserved
 *  * Auther:jin xingwang
 *
 */

package com.jwts.secret;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.*;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * 功能/模块
 * @author 靳兴旺
 * @version 1.0.0 2020-7-15
 * RSA加解密工具类
 */
public class RsaUtil {
    /** 公钥 */
    private static final String DEFAULT_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCOJh-dhRWjCaRmTIO3" +
            "xisoEEhE2NhQfMZXAssHDb5APmu-iHroP9me8RevfsgoyNO4di621PC4cNL2ITrrVeB5AF43Av1T62tslfTyyXlAW9rDE8aAC" +
            "FFgGTRNEtBIJ7YosqMZAL0OjPwoHuf03hIN3_EZOy3lL7cWLGQCTtmulwIDAQAB";

    /** 私钥 */
    private static final String DEFAULT_PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAI4mH52" +
            "FFaMJpGZMg7fGKygQSETY2FB8xlcCywcNvkA-a76Ieug_2Z7xF69-yCjI07h2LrbU8Lhw0vYhOutV4HkAXjcC_VPra2yV9PLJ" +
            "eUBb2sMTxoAIUWAZNE0S0EgntiiyoxkAvQ6M_Cge5_TeEg3f8Rk7LeUvtxYsZAJO2a6XAgMBAAECgYAQZcVv3lJIpGwagOkQ4" +
            "gNIc9hiWMFbR4l5sBDLl4JC1vCG4ieLcxIfi6A31mAwlDoYJUnuPVIndJxhWCP70cQ7BPCkXyDLxS4r1gmhVzLXZzjczE3mx7" +
            "qQ66zXRVAFC_fHHrZKCP5g5NB0NlPwBmtDcUxl9OezncMSsLihU3q8AQJBAM61W3Jq_DCOSp0GI6PVvIzsT5sXLl0soeK2MWH" +
            "8VvxaZNm-mAZAwNkocBsExDWiCn7vP2EaRfdMF-DjPQWjF9cCQQCwC7EKkoP0f-iripjwZpwekSAhXSHMsIFR7ePQAV-PsuPd" +
            "UJFBt9ZmCIJrGFeWcmNkziJopikxsJlkUDXXTEdBAkBFCH5M3PvyP2Mdw9IdQiFhMv52_ivmxCQgcdZWMg7e4lVx_twIopGFN" +
            "KMT8uiQPNiigcY56KtbJpeG7H0Rp-ErAkEAqeAeTp1jR6PF2QI84n2hWL2hy5Aa9XG82Fo94pHwrLFqwDzmbgv_jyXW85DhtA" +
            "NddgCTo3fzeEd5PRtJWR_6AQJBAKZa6QA5XutB1_p0R15vqpbA0tIAFva0fse_4k1YKB8uCvRKvR72PMz0SkbyEg3rjMWaye2" +
            "Cw8KS_yTOYHWgy6Y";

    /** 私钥 */
    private static RSAPrivateKey privateKey;

    /** 公钥 */
    private static RSAPublicKey publicKey;

    /** RSA最大加密明文大小 */
    private static final int MAX_ENCRYPT_BLOCK = 117;

    /** RSA最大解密密文大小 */
    private static final int MAX_DECRYPT_BLOCK = 128;

    private final static Logger logger = LoggerFactory.getLogger(RsaUtil.class);

    /**
     * 从文件流中加载公钥
     * @param in 公钥输入流
     * @throws Exception 操作流错误
     */
    public static void loadPublicKey(InputStream in) throws Exception{
        try {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
            String readline;
            StringBuilder stringBuilder = new StringBuilder();
            while((readline = bufferedReader.readLine()) != null){
                if (readline.charAt(0) != '-') {
                    stringBuilder.append(readline);
                    stringBuilder.append('\r');
                }
            }
            loadPublicKey(stringBuilder.toString());
        } catch (IOException e) {
            throw new Exception("公钥数据流读取错误");
        } catch (NullPointerException e) {
            throw new Exception("公钥输入流为空");
        }
    }

    /**
     * 从字符串加载公钥
     * @param publicKeyStr 公钥
     * @throws Exception 算法错误
     */
    public static void loadPublicKey(String publicKeyStr) throws Exception{
        try{
            byte[] buffer = Base64.encodeBase64String(publicKeyStr.getBytes()).getBytes();
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
            publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
        } catch (NoSuchAlgorithmException e){
            throw new Exception("无此算法");
        } catch (InvalidKeySpecException e){
            throw new Exception("公钥非法");
        } catch (NullPointerException e) {
            throw new Exception("公钥数据为空");
        }
    }

    public static void loadPublicKey() throws Exception{
        try{
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(DEFAULT_PUBLIC_KEY));
            publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
        } catch (NoSuchAlgorithmException e){
            throw new Exception("无此算法");
        } catch (InvalidKeySpecException e){
            throw new Exception("公钥非法");
        } catch (NullPointerException e) {
            throw new Exception("公钥数据为空");
        }
    }

    /**
     * 从文件中加载密钥
     * @param in 输入流
     * @throws Exception
     */
    public static void loadPrivateKey(InputStream in) throws Exception {
        try {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
            String readLine;
            StringBuilder stringBuilder = new StringBuilder();
            while((readLine = bufferedReader.readLine()) != null){
                if (readLine.charAt(0) != '-') {
                    stringBuilder.append(readLine);
                    stringBuilder.append('\r');
                }
            }
            loadPrivateKey(stringBuilder.toString());
        } catch (IOException e) {
            throw new Exception("私钥数据读取错误");
        } catch (NullPointerException e) {
            throw new Exception("私钥输入流为空");
        }
    }

    /**
     * 从字符串中加载密钥
     * @param privateKeyStr
     * @throws Exception 算法异常
     */
    public static void loadPrivateKey(String privateKeyStr) throws Exception {
        try {
            byte[] buffer = Base64.encodeBase64(privateKeyStr.getBytes());
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此算法");
        } catch (InvalidKeySpecException e) {
            throw new Exception("私钥非法");
        } catch (NullPointerException e) {
            throw new Exception("私钥数据为空");
        }
    }

    public static void loadPrivateKey() throws Exception {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(DEFAULT_PRIVATE_KEY));
            privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此算法");
        } catch (InvalidKeySpecException e) {
            throw new Exception("私钥非法");
        } catch (NullPointerException e) {
            throw new Exception("私钥数据为空");
        }
    }

    /**
     * 使用公钥明文加密
     * @param publicKey 公钥
     * @param plainText 明文
     * @return  密文
     * @throws Exception 加密异常
     */
    public static byte[] encrypt(RSAPublicKey publicKey, byte[] plainText) throws Exception{
        RSAPublicKey rsaPublicKey = publicKey == null ? getPublicKey() : publicKey;
        Cipher cipher;
        try{
            cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
            int inputLen = plainText.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;

            /* 对数据分段加密 */
            while (inputLen - offSet > 0){
                if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(plainText, offSet, MAX_ENCRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(plainText, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_ENCRYPT_BLOCK;
            }
            byte[] encryptedBytes = out.toByteArray();
            out.close();
            return encryptedBytes;
        } catch (UnsupportedEncodingException e){
            throw new Exception("不支持的编码方式");
        } catch (IllegalBlockSizeException e) {
            throw new Exception("明文长度非法");
        } catch (BadPaddingException e) {
            throw new Exception("明文数据已损坏");
        }
    }

    /**
     * 密钥加密
     * @param plainText 明文
     * @return 密文
     */
    public static byte[] encrypt (RSAPrivateKey privateKey, byte[] plainText) throws Exception{
        RSAPrivateKey rsaPrivateKey = privateKey == null ? getPrivateKey() : privateKey;
        Cipher cipher;
        try{
            cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, rsaPrivateKey);
            int inputLen = plainText.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            //对数据分段加密
            while (inputLen - offSet > 0){
                if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(plainText, offSet, MAX_ENCRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(plainText, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_ENCRYPT_BLOCK;
            }
            byte[] encryptedBytes = out.toByteArray();
            out.close();
            return encryptedBytes;
        } catch (IllegalBlockSizeException e) {
            throw new Exception("明文长度非法");
        } catch (BadPaddingException e) {
            throw new Exception("明文数据已损坏");
        }
    }

    /**
     * 公钥解密
     * @param publicKey 公钥
     * @param cipherData 密文
     * @return 明文
     * @throws Exception 解密异常
     */
    public static byte[] decrypt(RSAPublicKey publicKey, byte[] cipherData) throws Exception{
        RSAPublicKey rsaPublicKey = publicKey == null ? getPublicKey() : publicKey;
        Cipher cipher;
        try {
            cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.DECRYPT_MODE, rsaPublicKey);
            int inputLen = cipherData.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;

            /* 对数据分段解密 */
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                    cache = cipher.doFinal(cipherData, offSet, MAX_DECRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(cipherData, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_DECRYPT_BLOCK;
            }
            byte[] decryptedData = out.toByteArray();
            out.close();
            return decryptedData;
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此解密算法");
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
            return null;
        } catch (InvalidKeyException e) {
            throw new Exception("解密私钥非法,请检查");
        } catch (IllegalBlockSizeException e) {
            throw new Exception("密文长度非法");
        } catch (BadPaddingException e) {
            throw new Exception("密文数据已损坏");
        }
    }

    public static byte[] decrypt(RSAPrivateKey privateKey, byte[] cipherData) throws Exception{
        RSAPrivateKey rsaPrivateKey = privateKey == null ? getPrivateKey() : privateKey;
        try{
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.DECRYPT_MODE, rsaPrivateKey);
            int inputLen = cipherData.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;

            /* 对数据分段解密 */
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                    cache = cipher.doFinal(cipherData, offSet, MAX_DECRYPT_BLOCK);
                }else {
                    cache = cipher.doFinal(cipherData, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_DECRYPT_BLOCK;
            }
            byte[] decryptedData = out.toByteArray();
            out.close();
            return decryptedData;
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此解密算法");
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
            return null;
        } catch (InvalidKeyException e) {
            throw new Exception("解密私钥非法,请检查");
        } catch (IllegalBlockSizeException e) {
            throw new Exception("密文长度非法");
        } catch (BadPaddingException e) {
            throw new Exception("密文数据已损坏");
        }
    }

    /**
     * 传入密文，公钥加密
     * @param value 明文
     * @return 密文
     */
    public static String encodeByPublicKey(String value) {
        String data = "";
        try {
            byte[] cipher = encrypt(getPublicKey(), value.getBytes());
            data = Base64.encodeBase64String(cipher);
            data = data.replaceAll("[\\t\\n\\r]", "");
            logger.info("公钥加密结果：" + data);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("公钥加密异常:" + e.getMessage());
        }
        return data;
    }

    /**
     * 传入密文，公钥解密
     * @param value 密文
     * @return 明文
     */
    public static String decodeByPublicKey(String value) {
        String data = "";
        try{
            byte[] plainText = decrypt(getPublicKey(),Base64.decodeBase64(value.getBytes()));
            data = Base64.encodeBase64String(plainText);
            logger.info("公钥解密后的结果：" + data);
        } catch (Exception e){
            logger.error("公钥解密异常：" + e.getMessage());
        }
        return data;
    }

    /**
     * 传入明文，私钥加密
     * @param value 明文
     * @return 密文
     */
    public static String encodeByPrivateKey(String value) {
        String data = "";
        try {
            byte[] cipher = encrypt(getPrivateKey(), value.getBytes());
            data = Base64.encodeBase64String(cipher);
            data = data.replaceAll("[\\t\\r\\n]", "");
            logger.info("私钥加密后的结果：" + data);
        } catch (Exception e) {
            logger.error("私钥加密异常" + e.getMessage());
        }
        return data;
    }

    /**
     * 传入密文，通过密钥解密
     * @param value 密文
     * @return 明文
     */
    public static String decodeByPrivateKey(String value) {
        String data = "";
        try {
            byte[] plainText = decrypt(getPrivateKey(), Base64.decodeBase64(value.getBytes()));
            data = Base64.encodeBase64String(plainText);
            logger.info("私钥解密后结果：" + data);
        } catch (Exception e) {
            logger.info("私钥解密异常===" + e.getMessage());
        }
        return data;
    }

    /**
     * 获得公钥
     * @return 公钥
     * @throws Exception 流错误
     */
    private static RSAPublicKey getPublicKey() throws Exception{
        if (publicKey == null) {
            loadPublicKey();
        }
        return publicKey;
    }

    /**
     * 获得私钥
     * @return 私钥
     * @throws Exception 流错误
     */
    private static RSAPrivateKey getPrivateKey() throws Exception{
        if (privateKey == null){
            loadPrivateKey();
        }
        return privateKey;
    }
}
